<?php
/** 
 * @access public
 * @author Marcos Vinicius Pereira dos Santos, <marcos@eagletecnologia.com>
 */
class PDFReport extends ReportGenerator implements IReportGenerator {

    /**
     * @access private
     * @var String
     */
    private $outputType = null;

    /**
     * @access private
     * @var TCPDF
     */
    protected  $pdf = null;

    /**
     * @access private
     * @var Integer
     */
    private $cursor = null;

    /***************************************************************************
     *                               METHODS
     **************************************************************************/
    
    public function  __construct() {
        parent::__construct();
        
        $this->defaultFontFamily = "helvetica";       
        $this->outputType = "I";
        $this->cursor = 0;
    }

    /**
     * @access private
     * @param  Line $line
     * @return void
     */
    private function drawLine(Line $line){
        
        if($line->direction){
            $x1 = $this->convertX($line->x);
            $y1 = $this->convertY($line->y) + $line->height;
            $x2 = $this->convertX($line->x) + $line->width;
            $y2 = $this->convertX($line->y);
        }else{
            $x1 = $this->convertX($line->x);
            $y1 = $this->convertY($line->y);
            $x2 = $this->convertX($line->x) + $line->width;
            $y2 = $this->convertY($line->y) + $line->height;
        }      

        $this->pdf->Line($x1, $y1, $x2, $y2,$line->lineStyle);
    }   

    /**
     * @access private
     * @param  Rectangle $rectangle
     * @return void
     */
    private function drawRectangle(Rectangle $rectangle) {
        $x = $this->convertX($rectangle->x);
        $y = $this->convertY($rectangle->y);

        if($rectangle->radius <= $rectangle->height/2){
           $this->pdf->RoundedRect($x,$y,$rectangle->width,$rectangle->height,$rectangle->radius,'1111','DF',$rectangle->lineStyle,$rectangle->backcolor);
        }else{
            throw new Exception(" drawRectangle - Ainda nao existe suporte para este tamanho de radius!");
        }
    }

    /**
     * @access private
     * @param  StaticText $field
     * @return void
     */
    private function drawStaticText(StaticText $field){      
        
        //Verificando o modo de exibição do elemento.
        if($field->opaque){
            $this->pdf->SetFillColorArray($field->backcolor);
        }

        //Setando a posição do elemento na página e as configurações da fonte.
        $this->pdf->SetXY($this->convertX($field->x),$this->convertY($field->y));
        $this->pdf->SetFont($field->fontName,$this->getFontStyle($field->bold,$field->italic,$field->underline,$field->strikeThrongh),$field->fontSize);

        //Setando a cor de fundo e do texto.
        $this->pdf->SetTextColorArray($field->forecolor);

        $this->pdf->Cell($field->width,$field->height,$field->text,0,0,$field->hAlign,$field->opaque,'',0,false,'T',$field->vAlign);

        $this->drawBorderField($field);
    }

    /**
     *
     * @access private
     * @param  TextField $field Elemento que deverá ser desenhado no relatório.
     * @return void
     */
    private function drawTextField(TextField $field) {        
        $txt = $this->dataSets[$this->currentDataSet]->getValueExpression($field->fieldExpression);

        //Verificando o modo de exibição do elemento.
        if($field->opaque){
            $this->pdf->SetFillColorArray($field->backcolor);
        }       

        $this->pdf->SetFont($field->fontName,$this->getFontStyle($field->bold,$field->italic,$field->underline,$field->strikeThrongh),$field->fontSize);

        //Setando a cor de fundo e do texto.
        $this->pdf->SetTextColorArray($field->forecolor);

        $stringWidth = $this->pdf->GetStringWidth($txt);
        
        //Caso o tamanho do texto seja superio ao tamanho do field será quebrada linhas.
        if($field->stretchWhithOverflow && ($stringWidth > ($field->width - 5))){
            //Field com quebra de linha
            $this->pdf->MultiCell($field->width,$field->height, $txt,0, $field->vAlign,$field->opaque,1,$this->convertX($field->x), $this->convertY($field->y));

            //Atualizando a altura do field
            $field->height = ceil(ceil($stringWidth + 5) / $field->width) * $field->height;
            
            $this->updateElementHeightByChildrenElementHeight($this->currentBand, $field);
        }else{
             //Setando a posição do elemento na página e as configurações da fonte.
            $this->pdf->SetXY($this->convertX($field->x),$this->convertY($field->y));

            $this->pdf->Cell($field->width,$field->height,$txt,0,0,$field->hAlign,$field->opaque,'',0,false,'T',$field->vAlign);
        }      
        
        $this->drawBorderField($field);
    }

    /**
     * @access private
     * @param  Ellipse $ellipse
     * @return void
     */
    private function drawEllipse(Ellipse $ellipse) {
        
        $x = $this->convertX($ellipse->x + $ellipse->width/2);
        $y = $this->convertY($ellipse->y + $ellipse->height/2);
        $rx = $ellipse->width/2;
        $ry = $ellipse->height/2;
        $lineStyle = $ellipse->lineStyle;
        $fill = $ellipse->backcolor;
       
        $this->pdf->Ellipse($x,$y,$rx,$ry,0,0,360,'DF',$lineStyle,$fill);
    }

    /**
     * @access private
     * @param  Frame $frame
     * @return void
     */
    private function drawFrame(Frame $frame){
        $x = $this->convertX($frame->x);
        $y = $this->convertY($frame->y);

        if($frame->opaque){
           $this->pdf->Rect($x,$y,$frame->width,$frame->height,'DF',array('color'=>array(255,255,255)), $frame->backcolor);
        }
        
        if($frame->elements){
            foreach($frame->elements as $element){
                $this->drawElement($element);
            }
        }
        $this->drawBorderField($frame);
    }

    /**
     * @access private
     * @param  Barcode $barcode
     * @return void
     */
    private function drawBarcode(Barcode $barcode) {

    }

    /**
     * @access private
     * @param  Chart $chart
     * @return void
     */
    private function drawChart(Chart $chart) {
        /*$chartGenerator = new ChartGenerator();

        $chartGenerator->setChart($chart);
        $chartGenerator->setData($data);

        $file = $chartGenerator->generate();

        $x = $this->convertX($chart->x);
        $y = $this->convertY($chart->y);

        $this->pdf->Image($file,$x,$y,$chart->width,$chart->height,'','');
        $this->drawBorderField($chart);*/
    }

    /**
     * @access private
     * @param  Crosstab $crosstab
     */
    private function drawCrosstab(Crosstab $crosstab){
        
    }

    /**
     * @access private
     * @param  ListComponent $list
     */
    private function drawList(ListComponent $list){
        //Salvando o estado atual do cursor
        $cursorSave = $this->cursor;

        //Alterando o dataSet corrente
        $this->currentDataSet = $list->datasetRun->subDataset;
       
        $this->dataSets[$this->currentDataSet]->setDBConfig($this->dbConfig);
        $this->dataSets[$this->currentDataSet]->loadData();
      
        if($list->listComponents){
            //Verificando se o dataSet possui dados
            if($this->dataSets[$this->currentDataSet]->hasData()){

                while($this->dataSets[$this->currentDataSet]->nextRecord()){
                    
                    foreach($list->listComponents as $element){                       
                        $this->drawElement($element);                       
                    }                    
                    $this->cursor += $list->height;
                }
                //Voltando o cursor para o estado inicial
                $this->cursor = $cursorSave;
            }
        }
    }

    /**
     * @access private
     * @param Element $element
     */
    private function drawElement(Element $element) {
        
        if($element->printWhenExpression == '' || $this->dataSets[$this->currentDataSet]->getValueExpression($element->printWhenExpression)){

            switch($element->type) {
                case "STATICTEXT":
                    $this->drawStaticText($element);
                break;
                case "RECTANGLE":
                    $this->drawRectangle($element);
                break;
                case "LINE":
                    $this->drawLine($element);
                break;
                case "TEXTFIELD":
                    $this->drawTextField($element);
                break;
                case "ELLIPSE":
                    $this->drawEllipse($element);
                break;
                case "IMAGE":
                    $this->insertImage($element);
                break;
                case "FRAME":
                    $this->drawFrame($element);
                break;
                case "SUBREPORT":
                    $this->insertSubReport($element);
                break;
                case "CROSSTAB":
                    $this->drawCrosstab($element);
                break;
                case "CHART":
                    $this->drawChart($element);
                break;
                case "BREAK":
                    $this->insertBreakPoint($element);
                break;
                case "BARCODE":
                    $this->drawBarcode($element);
                break;
                case "LIST":
                    $this->drawList($element);
                break;
            }
        }        
    }

    /**
     * @access private
     * @param BreakPoint $breakPoint
     */
    private function insertBreakPoint(BreakPoint $breakPoint){        
        if($breakPoint->breakType == "PAGE"){
            $this->startNewPage();
        }else{

        }
    }

    /**
     * @access private
     * @param  Image $image
     * @return void
     */
    private function insertImage(Image $image) {
        $x = $this->convertX($image->x);
        $y = $this->convertY($image->y);       

        $this->pdf->Image($image->file,$x,$y,$image->width,$image->height,'','');
        $this->drawBorderField($image);
    }

    /**
     * @access private
     * @param  SubReport $subReport
     * @return void
     */
    private function insertSubReport(SubReport $subReport){
        $pdfSubReport = new PDFSubReport($subReport, $this->pdf);
        $pdfSubReport->generate();
    }

    /**
     * @access private
     * @param  Element $field
     * @return void
     */
    private function drawBorderField(Element $field){
        
        //Executa caso exista alguma borda.
        if($field->box){
            
            $x = $this->convertX($field->x);
            $y = $this->convertY($field->y);
            $w = $field->width;
            $h = $field->height;
            
            //Desenhando a borda superior.
            if($field->box->topPen){
                $this->pdf->Line($x, $y, $x+$w, $y,$field->box->topPen);
            }

            //Desenhando a borda esquerda.
            if($field->box->leftPen){   
                $this->pdf->Line($x, $y, $x, $y+$h,$field->box->leftPen);
            }

            //Desenhando a borda inferior.
            if($field->box->bottomPen){
                $this->pdf->Line($x, $y+$h, $x+$w, $y+$h,$field->box->bottomPen);
            }

            //Desenhando a borda direita.
            if($field->box->rightPen){
                $this->pdf->Line($x+$w, $y, $x+$w, $y+$h,$field->box->rightPen);
            }
        }
    }

    /**
     * @access private
     * @return void
     */
    private function addHeaders(){

        //Inseri a band title caso ela exista e se é a primeira página.
        if($this->pdf->getPage() == 1 && $this->bands['title']){
             $this->addBand($this->bands['title']);
            if($this->titleNewPage){
                $this->pdf->AddPage();
                $this->addBackGround();
            }            
        }
        
        if($this->bands['pageHeader']){
            $this->addBand($this->bands['pageHeader']);
        }
        
        if($this->bands['columnHeader']){            
            $this->addBand($this->bands['columnHeader']);
        }
    }

    /**
     * Método responsável por desenhar os elementos da band passsada como parâmentro.
     * 
     * @access private
     * @param  Band $band
     * @return void
     */
    private function addBand(Band $band) {

        if($band->printWhenExpression == '' || $this->dataSets[$this->currentDataSet]->getValueExpression($band->printWhenExpression)){
            $this->currentBand = &$band;

            if($band->items){
                foreach($band->items as $element){
                   $this->drawElement($element);
                }
            }
            $this->cursor += $band->height;
        }
    }
    
    /**
     * @access private
     * @param  Band $footer
     * @return void
     */
    private function addPageFooter(Band $footer){
        // Acrescendo 18 como forma de correcao.
        $this->cursor = $this->pageHeight - $this->topMargin - $this->bottomMargin - $footer->height + 28;
       
        $this->addBand($footer);
        
        $this->cursor = $this->topMargin;
    }

    /**
     * @access public
     * @param  String $type
     * @return void
     */
    public function setOutputType($type) {
        $this->outputType = $type;
    }    

    /**
     * @access private
     * @param Integer $x
     * @return Integer
     */
    private function convertX($x){
        return $x + $this->leftMargin;
    }

    /**
     * @access private
     * @param Integer $y
     * @return Integer
     */
    private function convertY($y){
        return $y + $this->cursor;
    }    

    /**
     * @access private
     * @param Boolean $bold
     * @param Boolean $italic
     * @param Boolean $underline
     * @param Boolean $stringThrough
     * @return String
     */
    private function getFontStyle($bold,$italic,$underline,$stringThrough) {
        $returnValue = "";
        
        $returnValue .= ($bold)?"B":"";
        $returnValue .= ($italic)?"I":"";
        $returnValue .= ($underline)?"U":"";
        $returnValue .= ($stringThrough)?"D":"";
        
        $returnValue = (empty($returnValue))?"R":$returnValue;

        return $returnValue;
    }    

    /**
     * @access protected
     * @return void
     */
    protected function configDocument() {
        $this->cursor = $this->topMargin;     

        $this->pdf = new TCPDF($this->pageOrientation, 'pt', PDF_PAGE_FORMAT, true, 'UTF-8', false);
        $this->pdf->setPrintHeader(false);
        $this->pdf->setPrintFooter(false);
        
        $this->pdf->SetAutoPageBreak(true,$this->bottomMargin);
        $this->pdf->SetMargins($this->leftMargin,$this->topMargin,$this->rightMargin);
    }

    /**
     *
     * @param Integer $groupIndex   
     * @param Integer $parentGroupIndex
     */
    private function addGroup($groupIndex,$parentGroupIndex = null){
        $group = $this->groups[$groupIndex];
     
        while($this->dataSets[$this->currentDataSet]->nextRecord()){
            $value = $this->dataSets[$this->currentDataSet]->record[$group->expression];
           
            if($parentGroupIndex !== null){
                $aux = $this->dataSets[$this->currentDataSet]->record[$this->groups[$parentGroupIndex]->expression];
                if($aux != $this->groups[$parentGroupIndex]->value){                    
                    break;
                }
            }
            
            if($value != $group->value) {               
                if($group->drawHeader) {
                    $this->pageBreak($group->header,false,$group->startNewPage);
                    $this->addBand($group->header);
                    $group->drawHeader = false;                   
                }else{             
                    $this->pageBreak($group->footer);
                    $this->addBand($group->footer);
                    $this->pageBreak($group->header,false,$group->startNewPage);
                    $this->addBand($group->header);                    
                }
                $group->value = $value;
                $this->dataSets[$this->currentDataSet]->resetVariables("group",$group->name);
            }

            if(!$group->drawHeader){
                $subGroup = $groupIndex + 1;
                if(array_key_exists($subGroup,$this->groups)){
                    $this->addGroup($subGroup,$groupIndex);
                }else{
                    $this->dataSets[$this->currentDataSet]->updateVariables();
                    $this->pageBreak($this->bands['detail']);
                    $this->addBand($this->bands['detail']);
                }
            }
            
        }
        
        if(!$group->drawHeader) {
            $this->pageBreak($group->footer);

            $this->addBand($group->footer);           
        }        
    }

    /**
     * Método responsável por adicionar o rodapé na última página do relatório.
     *
     * @access private
     * @return void
     */
    private function addLastPageFooter() {
        if($this->bands['lastPageFooter']) {
            if($this->bands['columnFooter']) {
                $this->addBand($this->bands['columnFooter']);
            }
            $this->addPageFooter($this->bands['lastPageFooter']);
        }else {
            if($this->bands['columnFooter']) {
                $this->addBand($this->bands['columnFooter']);
            }
            if($this->bands['pageFooter']) {
                $this->addPageFooter($this->bands['pageFooter']);
            }
        }
    }

    /**
     * Método responsável por adicionar a band summary no relatório.
     * 
     * @access private
     * @return void
     */
    private function addSummary(){      
            
        //Verificando se a band summary deve ficar em uma nova página.
        if($this->summaryNewPage) {

            $this->pdf->AddPage();
            $this->cursor = $this->topMargin;

            //Verificando se a nova página onde a band summary será inserida
            //deverará ter a band de rodapé.
            if($this->summaryWithPageHeaderAndFooter) {
                $this->addHeaders();
                $this->addBand($this->bands['summary']);
                $this->addLastPageFooter();
            }else {
                $this->addBand($this->bands['summary']);
            }
        }else {
            $this->addBand($this->bands['summary']);            
        }        
    }

    /**
     * @access private
     */
    private function noData() {
        switch($this->whenNoDataType) {
            case "NoDataSection":
                $this->pdf->AddPage();
                $this->addBand($this->bands['noData']);
                break;
            case "BlankPage":
                $this->pdf->AddPage();
                break;
            case "AllSectionsNoDetail":
                $this->pdf->AddPage();
                $this->addHeaders();
                $this->addLastPageFooter();
                break;
            default:
                return;
        }
    }

    /**
     * Método responsável por adicionar o background nas páginas do relatório.
     * Este método sempre é executado após o inicio de uma nova página.
     *
     * @access private
     * @return void
     */
    private function addBackGround(){
        //Verificando se a band background existe e se ela possui itens.
        if($this->bands['background'] && $this->bands['background']->items){
            
            foreach($this->bands['background']->items as $element){
                $this->drawElement($element);
            }
            
            //Retornando o curso para o inicio da página corrente.
            $this->cursor = $this->topMargin;
        }
    }
    
    /**
     * @access private  
     */
    private function drawReportWithoutGroups() {
        if($this->bands['detail']) {
            while($this->dataSets[$this->currentDataSet]->nextRecord()){
                $this->pageBreak($this->bands['detail']);

                $this->addBand($this->bands['detail']);
                
            };
        }
    }

    /**
     * @access private    
     */
    private function drawReportWithGroups(){       
        $this->addGroup(0);
    }

    /**
     * @access private
     * @return void
     * @uses   PDFReport::$sql
     * @uses   PDFReport::$groups
     * @uses   PDFReport::getData()
     * @uses   PDFReport::addHeaders()
     * @uses   PDFReport::drawReportWithGroups()
     * @uses   PDFReport::drawReportWithoutGroups()    
     */
    private function drawReport(){

        $this->dataSets[$this->currentDataSet]->loadData();
         
        //Verificando se existe dados
        if($this->dataSets[$this->currentDataSet]->hasData()) {
            
            //Recuperando o proximo record
            $this->dataSets[$this->currentDataSet]->nextRecord();
            
            $this->pdf->AddPage();
            $this->addBackGround();
            $this->addHeaders();
                       
            if($this->groups) {
                $this->drawReportWithGroups();
            }else {
                $this->drawReportWithoutGroups();
            }

            // Verificando se a band summary existe
            if($this->bands['summary']) {
                $this->addSummary();
            }else{
                $this->addLastPageFooter();
            }
            
        }else{
           $this->noData();
        }

        $this->pdf->Output($this->fileName.'.pdf', $this->outputType);
    }

    /**
     * Método responsável por finalizar a página corrente é iniciar uma nova página.
     * @access private
     * @return void
     */
    private function startNewPage(){
        //Verificando se existe a band de rodapé e se ela possui items;
        if($this->bands['pageFooter'] && $this->bands['pageFooter']->items){
            $this->addPageFooter($this->bands['pageFooter']);
        }
        $this->pdf->AddPage();
        $this->addBackGround(); 
        $this->addHeaders(); 
    }

    /**
     * @access private
     * @param Object $nextElement
     * @return Boolean
     */
    private function pageBreak($nextElement,$lastPage = false,$forcedBreak = false){
        if($forcedBreak){
            $this->startNewPage();
        }else{
            //Calculando a altura máxima permitida.
            $maxHeight = $this->pageHeight - $this->topMargin - $this->bottonMargin;

            //Calculando a proxima posicao para o curso.
            $nextHeight = $this->cursor + $nextElement->height;

            //Caso seja a ultima pagina e exista a band lastPageFooter, atualiza-se maxHeight.
            if($lastPage && $this->bands['lastPageFooter']) {
                $maxHeight -= $this->bands['lastPageFooter']->height;

            //Caso nao seja a ultima pagina ou caso nao exista a band lastPageFooter.
            //Verifica se existe a band pageFooter, caso exista atualiza-se maxHeight.
            }elseif($this->bands['pageFooter']) {
                $maxHeight -= $this->bands['pageFooter']->height;
            }

            if($nextHeight >= $maxHeight){
                $this->startNewPage();
            }
        }
    }

    /**
     * @access public
     * @return void
     */
    public function generate() {       
        $this->init();
        $this->configDocument();       
        $this->drawReport();        
    }
}
?>